<?php

namespace common\components\system;


use Elasticsearch\ClientBuilder;
use Elasticsearch\ConnectionPool\Selectors\RandomSelector;
use Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector;
use Elasticsearch\ConnectionPool\Selectors\StickyRoundRobinSelector;
use Elasticsearch\ConnectionPool\SimpleConnectionPool;
use Elasticsearch\ConnectionPool\SniffingConnectionPool;
use Elasticsearch\ConnectionPool\StaticConnectionPool;
use Elasticsearch\ConnectionPool\StaticNoPingConnectionPool;
use yii\base\Component;
use yii\base\Exception;

class ES extends Component
{
    /**
     * 主机
     * @var string|array
     */
    public $hosts;


    /**
     * 权限，账号密码
     * @var
     */
    public $auth;

    /**
     * 重试次数
     * @var int
     */
    public $retry;

    /**
     * 连接池类型
     * @var int
     */
    public $pool;

    /**
     * 连接池类名
     * @var string
     */
    public $poolClassName;

    /**
     * 选择器类型
     * @var int
     */
    public $selector;

    /**
     * 选择器类名
     * @var string
     */
    public $selectorClassName;

    /**
     * 连接池类型：StaticNoPingConnectionPool
     * var int
     */
    public const POOL_STATIC_NO_PING = 1;

    /**
     * 连接池类型：StaticConnectionPool
     * var int
     */
    public const POOL_STATIC = 2;

    /**
     * 连接池类型：SimpleConnectionPool
     * var int
     */
    public const POOL_SIMPLE = 3;

    /**
     * 连接池类型：SniffingConnectionPool
     * var int
     */
    public const POOL_SNIFFING = 4;

    /**
     * 连接池类型：用户自定义
     * var int
     */
    public const POOL_CUSTOM = 99;

    /**
     * 选择器类型：RoundRobinSelector
     * @var int
     */
    public const SELECTOR_ROUND_ROBIN = 1;

    /**
     * 选择器类型：StickyRoundRobinSelector
     * @var int
     */
    public const SELECTOR_STICKY_ROUND_ROBIN = 2;

    /**
     * 选择器类型：RandomSelector
     * @var int
     */
    public const SELECTOR_RANDOM = 3;

    /**
     * 选择器类型：用户自定义
     * @var int
     */
    public const SELECTOR_CUSTOM = 99;

    private $_client;

    /**
     * 连接池类型MAP
     * @var array
     */
    private static $_poolMap = [
        self::POOL_STATIC_NO_PING => StaticNoPingConnectionPool::class,
        self::POOL_STATIC => StaticConnectionPool::class,
        self::POOL_SIMPLE => SimpleConnectionPool::class,
        self::POOL_SNIFFING => SniffingConnectionPool::class,
    ];

    /**
     * 选择器类型MAP
     * @var array
     */
    private static $_selectorMap = [
        self::SELECTOR_ROUND_ROBIN => RoundRobinSelector::class,
        self::SELECTOR_STICKY_ROUND_ROBIN => StickyRoundRobinSelector::class,
        self::SELECTOR_RANDOM => RandomSelector::class,
    ];

    /**
     * 重写GET魔术方法
     * @param string $name
     * @return mixed
     * @throws \yii\base\UnknownPropertyException
     */
    public function __get($name)
    {
        if (property_exists($this->_client, $name))
        {
            return $this->_client->{$name};
        }

        return parent::__get($name);
    }

    /**
     * 重写SET魔术方法
     * @param string $name
     * @param mixed $value
     * @throws \yii\base\UnknownPropertyException
     */
    public function __set($name, $value)
    {
        if (property_exists($this->_client, $name))
        {
            $this->_client->{$name} = $value;
        }

        return parent::__set($name, $value);
    }

    /**
     * 重写CALL魔术方法
     * @param string $name
     * @param array $params
     * @return mixed
     */
    public function __call($name, $params)
    {
        if (method_exists($this->_client, $name))
        {
            return $this->_client->{$name}(...$params);
        }

        return parent::__call($name, $params);
    }


    /**
     * @throws Exception
     */
    public function init()
    {
        parent::init();

        $this->checkClassName();

        $this->_client = ClientBuilder::fromConfig($this->getConfig(), FALSE);
    }

    /**
     * 获取客户端
     * @return \Elasticsearch\ClientBuilder
     */
    public function getClient(): ClientBuilder
    {
        return $this->_client;
    }

    /**
     * 获取客户端配置
     * @return array
     */
    private function getConfig(): array
    {
        $config = [
            'hosts' => (array) $this->hosts,
        ];

        if (!is_null($this->retry))
        {
            $config['retries'] = $this->retry;
        }

        if ($this->poolClassName)
        {
            $config['connectionPool'] = $this->poolClassName;
        }

        if ($this->selectorClassName)
        {
            $config['selector'] = $this->selectorClassName;
        }

        return $config;
    }


    /**
     * @throws Exception
     */
    private function checkClassName(): void
    {
        $checkMap = [
            'pool' => [
                'className' => 'poolClassName',
                'customType' => self::POOL_CUSTOM,
                'map' => self::$_poolMap,
            ],
            'selector' => [
                [
                    'className' => 'selectorClassName',
                    'customType' => self::SELECTOR_CUSTOM,
                    'map' => self::$_selectorMap,
                ],
            ]
        ];

        foreach ($checkMap as $type => $params)
        {
            if (!$this->{$type})
            {
                return;
            }

            if (in_array($this->{$type}, array_keys($params['map'])))
            {
                $this->{$params['className']} = $params['map'][$this->{$type}];

                return;
            }

            if ($this->{$type} == $params['customType'])
            {
                if (!class_exists($this->{$params['className']}))
                {
                    throw new Exception("The {$type} classname({$this->{$params['className']}}) is invalid.");
                }

                return;
            }

            throw new Exception("The param {$type}({$this->{$type}}) is invalid.");
        }
    }
}